Automatic differentiation & GradientTape

신경망을 최적화하려면 신경망의 가중치에 대한 비용의 그레이디언트를 계산해야 한다.
그 외에도 그레디언트는 다양하게 사용된다.
ex) 신경망 모델이 한 테스트 샘플에 대해 특정 예측을 만드는지 분석하는데 사용된다.
자동 미분(Automatic Differentiation)
텐서플로는 자동 미분을 지원하며,
중첩된 함수의 그레이디언트를 계산하기 위해 연쇄 법칙(chain rule)을 구현한 것으로 생각 할 수 있다.
출력이나 계산 중간에 텐서를 만드는 일련의 연산을 정의할 때, 텐서플로는 계산 그래프 안에서 의존성을 가지는 노드에 대해
이런 텐서의 그레이디언트를 계산하는 기능을 제공한다.
tf.GradientTape
훈련가능한 변수에 대한 손실 그레이디언트
w=tf.Variable(1.0)
b=tf.Variable(0.5)
print(w.trainable, b.trainable)

True True

x=tf.convert_to_tensor([1.4])
y=tf.convert_to_tensor([2.1])
with tf.GradientTape() as tape:
z=tf.add(tf.multiply(w, x), b)
loss=tf.reduce_sum(tf.square(y-z))
dloss_dw=tape.gradient(loss, w)
tf.print('dL/dw:', dloss_dw)
tf.print(2*x*(w*x+b-y))

dL/dw: -0.559999764

[-0.559999764]

도함수 dy/dx는 두 가지 방법으로 계산할 수 있다.
u0=x
u1=h(x)
u2=g(u1)
u3=f(u2)=y

1. du3/dx=(du3/du2)(du2/du0)(전진 모드)
2. dy/du0=(dy/du1)(du1/du0)(후진 모드)

전진 모드는 뒤쪽의 도함수 값이 먼저 계산되고 앞쪽의 값이 누적된다.
후진 모드는 앞쪽의 도함수 값이 먼저 계산되고 뒤쪽의 값이 누적된다.

텐서플로는 후진모드 자동 미분을 사용한다.
훈련하지 않는 변수에 대한 그레이디언트
tf.GradientTape는 훈련 가능한 변수를 위한 그레이디언트를 기본으로 지원한다.
훈련되지 않는 변수와 다른 Tensor 객체일 경우에는 tape.watch()를 호출해서 이 변수도 추적해 달라고 GradientTape에 알려줘야 한다.
with tf.GradientTape() as tape:
tape.watch(x)
z=tf.add(tf.multiply(w, x), b)
loss=tf.reduce_sum(tf.square(y-z))
dloss_dx=tape.gradient(loss, x)
tf.print('dL/dx:', dloss_dx)

dL/dx: [-0.399999857]

적대 샘플
입력 샘플에 대한 손실의 그레이디언트 계산은 적대 샘플(adversarial example)또는 적대 공격(adversarial attack)을 생성하는데 사용된다.
컴퓨터 비전에서 적대 샘플은 눈치채기 어려운 작은 잡음(또는 변경)을 입력 샘플에 추가하여 만드는 샘플이다.
심층 심경망이 이 샘플을 잘못 분류하게 만든다.
여러개의 그레이디언트 계산
tf.GradientTape 컨텍스트에서 계산을 모니터링할 때, 기본적으로 하나의 그레이디언트 계산을 위한 자원만 유지한다.
tape.gradient()를 한번 호출하면 이 자원이 반납되고 테이프는 초기화 된다.
따라서 여러개의 그레이디언트를 계산할려면 persistent 매개변수를 True로 설정하여 테이프를 시작해야 한다.
with tf.GradientTape(persistent=True) as tape:
z=tf.add(tf.multiply(w, x), b)
loss=tf.reduce_sum(tf.square(y-z))
dloss_dw=tape.gradient(loss, w)
dloss_db=tape.gradient(loss, b)
tf.print('dL/dw:', dloss_dw)
tf.print('dL/db:', dloss_db)

dL/dw: -0.559999764

dL/db: -0.399999857

그레이디언트를 한개 이상 계산하고 싶을 때만 사용하는 것이 좋다.(메모리 비효율적인, persistent=False가 기본값)
모델 파라미터에 그라이디언트 값 적용하기
옵티마이저를 정의하고 keras의 API(appy_gradients)를 통해서 모델 파라미터를  최적화
optimizer=tf.keras.optimizers.SGD()
optimizer.apply_gradients(zip([dloss_dw, dloss_db], [w, b]))
tf.print(' w:', w)
tf.print(' b:', b)

업데이트 된 w: 1.0056

업데이트 된 b: 0.504